import {Game} from "@/Game/Game";
import {defaultClearShopGroundPrice, defaultDishDrawPile, PlayerArea} from "@/Game/PlayerArea";
import {AllDishListAsc} from "@/Game/Dish";
import {GetRandomInt} from "@/Game/CommonMethods";
import {EventEffect} from "@/Game/Event";
import {CustomerClass} from "@/Game/Customer";

export enum PlayerPurchaseType {
    None,
    Dish,
    Shop
}

export class PlayerPurchaseOption {
    Type: PlayerPurchaseType;
    Index: number;

    constructor(type: PlayerPurchaseType, index: number = -1) {
        this.Type = type;
        this.Index = index;
    }
}

export const defaultPlayerPurchaseOption = new PlayerPurchaseOption(PlayerPurchaseType.None);

export class PlayerServeOption {
    CustomerIndex: number;
    ShopIndex: number;

    constructor(customerIndex: number, shopIndex: number) {
        this.CustomerIndex = customerIndex;
        this.ShopIndex = shopIndex;
    }
}

export const defaultPlayerServeOption = new PlayerServeOption(-1, -1);

export class Robot {
    Name: string;

    constructor(name: string) {
        this.Name = name;
    }

    playerPurchase = (game: Game): PlayerPurchaseOption => defaultPlayerPurchaseOption;
    playerTrim = (game: Game): number => -1;
    playerServe = (game: Game): PlayerServeOption => defaultPlayerServeOption;
}

export enum PurchaseShopBehavior {
    Desc,
    Asc,
    Random,
    LastCustomerPrefer
}

export enum ServeCustomerBehavior {
    Last,
    HighestDishIncome,
    HighestAllIncome// 覆盖 SelectShopWhenServingBehavior 为 HighestIncome
}

export enum SelectShopWhenServingBehavior {
    First,
    Random,
    HighestIncome
}

export class AdjustableRobot extends Robot {
    maxFirstDish: number;
    maxSecondDish: number;
    maxThirdDish: number;
    leastMoneyToTrimLastDish: number;
    leastMoneyToTrimThirdDish: number;
    leastMoneyToPurchaseShop: number;
    leastFirstDishToPurchaseOthers: number;
    allowDuplicateShop: boolean;
    purchaseShopBehavior: PurchaseShopBehavior;
    serveCustomerBehavior: ServeCustomerBehavior;
    selectShopWhenServingBehavior: SelectShopWhenServingBehavior;

    playerArea: PlayerArea;
    playerOwnedDish: number[];
    playerOwnedDishTotalCount: number = 0;
    achievedThirdDishCount: boolean = false;
    achievedLeastFirstDish: boolean = false;

    constructor(playerArea: PlayerArea, maxFirstDish: number = 1, maxSecondDish: number = Infinity, maxThirdDish: number = 4, leastMoneyToPurchaseShop: number = 0, leastFirstDishToPurchaseOthers: number = 0, leastMoneyToTrimLastDish: number = 9, leastMoneyToTrimThirdDish: number = 15, allowDuplicateShop: boolean = true, purchaseShopBehavior: PurchaseShopBehavior = PurchaseShopBehavior.Desc, serveCustomerBehavior: ServeCustomerBehavior = ServeCustomerBehavior.Last, selectShopWhenServingBehavior: SelectShopWhenServingBehavior = SelectShopWhenServingBehavior.First) {
        super("可调机器人");
        this.playerArea = playerArea;
        this.maxFirstDish = maxFirstDish;
        this.maxSecondDish = maxSecondDish;
        this.maxThirdDish = maxThirdDish;
        this.leastMoneyToPurchaseShop = leastMoneyToPurchaseShop;
        this.leastFirstDishToPurchaseOthers = leastFirstDishToPurchaseOthers;
        this.leastMoneyToTrimLastDish = leastMoneyToTrimLastDish;
        this.leastMoneyToTrimThirdDish = leastMoneyToTrimThirdDish;
        this.allowDuplicateShop = allowDuplicateShop;
        this.purchaseShopBehavior = purchaseShopBehavior;
        this.serveCustomerBehavior = serveCustomerBehavior;
        this.selectShopWhenServingBehavior = selectShopWhenServingBehavior;
        this.playerOwnedDishTotalCount = defaultDishDrawPile.length;
        this.playerOwnedDish = new Array(AllDishListAsc.length).fill(0);
        for (let dishClass of defaultDishDrawPile) {
            this.playerOwnedDish[dishClass]++;
        }
        this.achievedThirdDishCount = this.playerOwnedDish[1] >= maxThirdDish;
    }

    playerPurchase = (game: Game) => {
        let moneyOwned = this.playerArea.moneyOwned;
        let commonAreaDishCount = game.commonArea.dishCount;
        if (this.playerOwnedDish[3] <= this.maxFirstDish && moneyOwned >= AllDishListAsc[3].Price) {
            if (commonAreaDishCount[3] > 0) {
                this.playerOwnedDish[3]++;
                if (++this.playerOwnedDish[3] >= this.leastFirstDishToPurchaseOthers) {
                    this.achievedLeastFirstDish = true;
                }
                return new PlayerPurchaseOption(PlayerPurchaseType.Dish, 0);
            }
            else {
                this.achievedLeastFirstDish = true;
            }
        }
        let shopOwnedLength = this.playerArea.shopOwned.length;
        if (this.achievedLeastFirstDish) {
            if (shopOwnedLength < 8 && moneyOwned >= this.leastMoneyToPurchaseShop) {
                let shopQueue = game.commonArea.shopQueue;
                let clearShopGroundPrice = defaultClearShopGroundPrice[shopOwnedLength];
                let affordableIndexes: number[] = [];
                for (let i = 0; i < shopQueue.length; i++) {
                    let shop = shopQueue[i];
                    if (shop.Price + clearShopGroundPrice <= moneyOwned && (this.allowDuplicateShop || !this.playerArea.shopOwned.some(s => s.ShopType == shop.ShopType))) {
                        affordableIndexes.push(i);
                    }
                }
                if (affordableIndexes.length > 0) {
                    switch (this.purchaseShopBehavior) {
                        case PurchaseShopBehavior.Desc:
                            return new PlayerPurchaseOption(PlayerPurchaseType.Shop, affordableIndexes[affordableIndexes.length - 1]);
                        case PurchaseShopBehavior.Asc:
                            return new PlayerPurchaseOption(PlayerPurchaseType.Shop, affordableIndexes[0]);
                        case PurchaseShopBehavior.Random:
                            return new PlayerPurchaseOption(PlayerPurchaseType.Shop, affordableIndexes[GetRandomInt(0, affordableIndexes.length - 1)]);
                        case PurchaseShopBehavior.LastCustomerPrefer:
                            let preferShopIndex = -1;
                            let preferShopPrice = -1;
                            let lastCustomerDestinations = game.commonArea.customerQueue[game.commonArea.customerQueue.length - 1].Destinations;
                            for (let i of affordableIndexes) {
                                let shop = shopQueue[i];
                                if (lastCustomerDestinations.includes(shop.ShopType) && shop.Price > preferShopPrice) {
                                    preferShopIndex = i;
                                    preferShopPrice = shop.Price;
                                }
                            }
                            if (preferShopIndex > -1) {
                                return new PlayerPurchaseOption(PlayerPurchaseType.Shop, preferShopIndex);
                            }
                            return new PlayerPurchaseOption(PlayerPurchaseType.Shop, affordableIndexes[0]);
                    }
                }
            }
            if (this.playerOwnedDish[2] < this.maxSecondDish && moneyOwned >= AllDishListAsc[2].Price && commonAreaDishCount[2] > 0) {
                this.playerOwnedDish[2]++;
                return new PlayerPurchaseOption(PlayerPurchaseType.Dish, 1);
            }
            if (!this.achievedThirdDishCount && this.playerOwnedDish[1] < this.maxThirdDish && moneyOwned >= AllDishListAsc[1].Price && commonAreaDishCount[1] > 0) {
                if (++this.playerOwnedDish[1] >= this.maxThirdDish) {
                    this.achievedThirdDishCount = true;
                }
                return new PlayerPurchaseOption(PlayerPurchaseType.Dish, 2);
            }
        }
        return new PlayerPurchaseOption(PlayerPurchaseType.None);
    };
    playerTrim = (game: Game) => {
        let moneyOwned = this.playerArea.moneyOwned;
        if (moneyOwned >= 3 && this.playerArea.dishDiscardCount > 0) {
            if (moneyOwned >= this.leastMoneyToTrimLastDish && this.playerArea.dishDiscardPile[0] > 0) {
                this.playerOwnedDish[0]--;
                return 0;
            }
            if (this.achievedThirdDishCount && moneyOwned >= this.leastMoneyToTrimThirdDish && this.playerArea.dishDiscardPile[1] > 0) {
                this.playerOwnedDish[1]--;
                return 1;
            }
        }
        return -1;
    };

    calculateDishIncome(customer: CustomerClass, shouldConsume: number): number {
        let earnedFromDish = 0;
        let dishDrawPileLength = 0;
        let dishDrawPile: number[] = new Array(AllDishListAsc.length);
        for (let i = 0; i < AllDishListAsc.length; i++) {
            let count = this.playerOwnedDish[i] - this.playerArea.dishDiscardPile[i];
            dishDrawPile[i] = count;
            dishDrawPileLength += count;
        }
        if (dishDrawPileLength + this.playerArea.dishDiscardCount > 0) {
            let notConsumed = shouldConsume;
            while (notConsumed > 0) {
                if (notConsumed >= dishDrawPileLength) {
                    notConsumed -= dishDrawPileLength;
                    for (let i = 0; i < AllDishListAsc.length; i++) {
                        let dish = AllDishListAsc[i];
                        let count = dishDrawPile[i];
                        earnedFromDish += dish.DirectIncome * count;
                        if (dish.RandomIncome) {
                            earnedFromDish += 3.5 * count;
                        }
                    }
                    dishDrawPileLength = this.playerOwnedDishTotalCount;
                    dishDrawPile = [...this.playerOwnedDish];
                }
                else {
                    let factor = notConsumed / dishDrawPileLength;
                    let temp = 0;
                    for (let i = 0; i < AllDishListAsc.length; i++) {
                        let dish = AllDishListAsc[i];
                        let count = dishDrawPile[i];
                        temp += dish.DirectIncome * count;
                        if (dish.RandomIncome) {
                            temp += 3.5 * count;
                        }
                    }
                    earnedFromDish += temp * factor;
                    notConsumed = 0;
                }
            }
        }
        return earnedFromDish;
    }

    playerServe = (game: Game) => {
        let customerIndex = -1;
        if (this.serveCustomerBehavior == ServeCustomerBehavior.Last) {
            customerIndex = game.commonArea.customerQueue.length - 1;
        }
        else {
            let customerQueueLength = game.commonArea.customerQueue.length;
            if (this.serveCustomerBehavior == ServeCustomerBehavior.HighestDishIncome) {
                let maxDishIncome: number = 0;
                for (let i = 0; i < customerQueueLength; i++) {
                    let customer = game.commonArea.customerQueue[i];
                    let shouldConsume = this.playerArea.getShouldConsume(customer);
                    let customersCountInFront = customerQueueLength - i;
                    if (customersCountInFront <= this.playerArea.moneyOwned) {
                        let temp = this.calculateDishIncome(customer, shouldConsume) + customer.Fund - customersCountInFront;
                        if (temp >= maxDishIncome) {
                            maxDishIncome = temp;
                            customerIndex = i;
                        }
                    }
                }
            }
            else {
                let allIncome: number[] = new Array(customerQueueLength);
                let shopOwnedLength = this.playerArea.shopOwned.length;
                let resolveOneShop = game.commonArea.persistentEvent && game.commonArea.persistentEvent.EffectType == EventEffect.ResolveOneShop && this.playerArea.shopOwned.length > 1;
                let selectShop: number[] = resolveOneShop ? new Array(shopOwnedLength) : [];
                for (let i = 0; i < customerQueueLength; i++) {
                    let customersCountInFront = customerQueueLength - i - 1;
                    if (customersCountInFront > this.playerArea.moneyOwned) {
                        allIncome[i] = -customersCountInFront;
                        continue;
                    }
                    let customer = game.commonArea.customerQueue[i];
                    if (resolveOneShop) {
                        let dishAndShopIncome: number[] = new Array(shopOwnedLength);
                        for (let j = 0; j < shopOwnedLength; j++) {
                            let shop = this.playerArea.shopOwned[j];
                            let shouldConsume = this.playerArea.getShouldConsume(customer, shop);
                            dishAndShopIncome[j] = this.calculateDishIncome(customer, shouldConsume) + (customer.Destinations.includes(shop.ShopType) ? this.playerArea.resolveShop(this.playerArea.shopOwned[j], shouldConsume) : 0);
                        }
                        let maxShopIncome = Math.max(...dishAndShopIncome);
                        allIncome[i] = maxShopIncome + customer.Fund - customersCountInFront;
                        if (this.selectShopWhenServingBehavior == SelectShopWhenServingBehavior.Random) {
                            let maxIndexes = [];
                            for (let j = 0; j < shopOwnedLength; j++) {
                                if (dishAndShopIncome[j] == maxShopIncome) {
                                    maxIndexes.push(j);
                                }
                            }
                            selectShop[i] = maxIndexes[GetRandomInt(0, maxIndexes.length - 1)];
                        }
                        else {
                            selectShop[i] = dishAndShopIncome.indexOf(maxShopIncome);
                        }
                    }
                    else {
                        let shouldConsume = this.playerArea.getShouldConsume(customer);
                        let shopIncome = 0;
                        for (let j = 0; j < shopOwnedLength; j++) {
                            let shop = this.playerArea.shopOwned[j];
                            if (customer.Destinations.includes(shop.ShopType)) {
                                shopIncome += this.playerArea.resolveShop(shop, shouldConsume);
                            }
                        }
                        allIncome[i] = this.calculateDishIncome(customer, shouldConsume) + shopIncome + customer.Fund - customersCountInFront;
                    }
                }
                let maxAllIncome = 0;
                for (let i = 0; i < customerQueueLength; i++) {
                    let temp = allIncome[i];
                    if (temp >= maxAllIncome) {
                        maxAllIncome = temp;
                        customerIndex = i;
                    }
                }
                return new PlayerServeOption(customerIndex, selectShop[customerIndex]);
            }
        }
        let shopIndex = -1;
        if (game.commonArea.persistentEvent && game.commonArea.persistentEvent.EffectType == EventEffect.ResolveOneShop && this.playerArea.shopOwned.length > 1) {
            switch (this.selectShopWhenServingBehavior) {
                case SelectShopWhenServingBehavior.First:
                    shopIndex = 0;
                    break;
                case SelectShopWhenServingBehavior.Random:
                    shopIndex = GetRandomInt(0, this.playerArea.shopOwned.length - 1);
                    break;
                case SelectShopWhenServingBehavior.HighestIncome:
                    let customer = game.commonArea.customerQueue[customerIndex];
                    let maxShopIncome: number = 0;
                    for (let j = 0; j < this.playerArea.shopOwned.length; j++) {
                        let shop = this.playerArea.shopOwned[j];
                        let shopIncome = customer.Destinations.includes(shop.ShopType) ? this.playerArea.resolveShop(shop, this.playerArea.getShouldConsume(customer, shop)) : 0;
                        if (shopIncome > maxShopIncome) {
                            maxShopIncome = shopIncome;
                            shopIndex = j;
                        }
                    }
                    break;
            }
        }
        return new PlayerServeOption(customerIndex, shopIndex);
    };
}